#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>  //para utilizar optind, tambien "getopt.h"
					 //The variable optind is the index of the next element of 
					 //the argv[] vector to be processed. It shall be initialized 
					 //to 1 by the system, and getopt() shall update it when it finishes 
					 //with each element of argv[]. When an element of argv[] contains 
					 //multiple option characters, it is unspecified how getopt() determines 
					 //which options have already been processed.
#define FICH "whois.conf"
#define MAXSERVFICH 50
#define MAXLENSERV 50
#define MAXTLD 4

#define DEBUG 0
#define TCP 0
//#define WHOIS_PORT "43"
#define MAX_TAM_BUF 5*1024

/* Regional Internet Registries (RIR) */
#define ARIN	"whois.arin.net" 	// North America
#define RIPE	"whois.ripe.net"	// Europe
#define APNIC	"whois.apnic.net" 	// Asia
#define LACNIC	"whois.lacnic.net"	// South America
#define AFRINIC "whois.afrinic.net"	// Africa

/* Función que dado un dominio del tipo "www.google.com" devuelve 
en el parámetro TLD lo que hay despues del último punto
(en este ejemplo devolveria "com").
Si la función devuelve 0, el parámetro de salida TLD este válido
si la funcion devuelve -1, la información que haya en TLD no es válida
*/

struct servidor_whois* servfich[MAXSERVFICH];

struct servidor_whois{
	char tld[MAXTLD];
	char serv[MAXLENSERV];
};
const char* nombre_prog;
char *whois_port = "43";			// antes int
int verbose = 0;			// flag para activar el modo verbose

int leerTLD(char * dominio,char *TLD)
{
	int i=2;
	char *final=dominio+strlen((const char *)dominio)-1;
	TLD[3]='\0';
	while ( ((*final)!='.') && (isalpha(*final)) && (i>=0) )
	{
		TLD[i]=*final;
		final=final-1;
		i=i-1;
	}
	if(*final=='.')
	{
		if (i==0)
		{
			int j=i+1;
			while(TLD[j]!='\0')
			{
				TLD[j-1]=TLD[j];
				j=j+1;
			}
			TLD[j-1]=TLD[j];
			return 0; 		
		}
		else if (i==-1)
			return 0;
		else
			return -1;
	}
	else
	{
		return -1;
	}
}

char * encontrar_rir(char *mensaje)
{
	char *rir;
	rir = strstr(mensaje, "OrgID:      RIPE");
    if(rir != NULL) return RIPE;
	
    rir = strstr(mensaje, "OrgID:      APNIC");
    if(rir != NULL) return APNIC;
	
    rir = strstr(mensaje, "OrgID:      LACNIC");
    if(rir != NULL) return LACNIC;
	
    rir = strstr(mensaje, "OrgID:      AFRINIC");
    if(rir != NULL) return AFRINIC;
	
    // Not sure if they still use the OrgID
    rir = strstr(mensaje, "whois.afrinic.net");
    if(rir != NULL) return AFRINIC;
	
    return ARIN;
}

/*
 Esta función resuelve la dirección del servidor whois 
 que hay que consultar por defecto y devuelve su dirección ip.
 EL parámetro dominio debe ser un dominio válido, no puede ser una ip u otra cosa.
 */
char * resolver_dir_serv(char *dominio)
{
	char servidor[23];
	struct hostent *host;
	//LeerTLD
	if(leerTLD(dominio,servidor)==-1)
	{
		fprintf(stderr,"Error al buscar el TLD en el dominio especificado\n");
		exit(-1);
	}
	if (verbose)
		printf("TLD leeido correctamente.\n");
	char *serv=strcat(servidor,".whois-servers.net");		// TODO: pq .whois-servers.net
#if DEBUG
	fprintf(stderr,"servidor consultado: %s\n",serv);
#endif
	//resolver la dirección del sevidor
	if ((host=gethostbyname(serv))!=NULL)
	{
#if DEBUG
		fprintf(stderr,"Official name of the host\n");
		fprintf(stderr,"%s\n", host->h_name);
		int i=0;
		fprintf(stderr,"Alternative names of the hosts\n");
		while(stderr,host->h_aliases[i]!=NULL)
		{
			fprintf(stderr,"%s\n",host->h_aliases[i]);	
			i++;
		}
		i=0;
		fprintf(stderr,"%s\n",inet_ntoa(*((struct in_addr *)host->h_addr)));
#endif
		//Pasar de formato de ordenacion de la red a ascii
		char *ip=(char *)inet_ntoa(*((struct in_addr *)host->h_addr));
		if(verbose)
		{
			printf("Consulta al servidor dns por el servidor whois %s finalizada\n",ip);
		}
		return ip;
	}
	else
	{
		fprintf(stderr,"Dominio del servidor %s desconocido",serv);
		herror("");
		exit(-1);
	}
}
/* Esta función devuelve la dirección del servidor whois 
que hay que consultar para un dominio o 
o el servidor ARIN en caso de que fuera una ip*/
char* resolver_servidor(char * argumento,int flag_f)
{
	struct sockaddr_in comp;
	struct hostent *h;
	char *arin;
	char * ip_servidor;
	int mem;
	char eltld[MAXTLD];
	char servidor[MAXLENSERV];
	unsigned int i=0;
	if(flag_f==1)
	{
		if (inet_pton(AF_INET,argumento,&comp.sin_addr)<=0)
		{
			if(verbose)
			{
				printf("leyendo servidores del fichero...\n");
			}
			mem=leer_servidores_fichero(servfich);// variable servfich global,leer_serv_fich pide memoria con malloc,
										//hay que liberarla con free tan veces como devuelva la función.
			if(leerTLD(argumento,eltld)==-1)
			{
				fprintf(stderr,"Error al buscar el TLD en el dominio especificado\n");
				exit(-1);
			}
			while((i<mem) && ((strcmp(servfich[i]->tld,eltld))!=0))
			{
				i+=1;
			}
			if (i==mem)
			{
				fprintf(stderr,"Servidor no encontrado en el fichero de configuracion whois.conf\n");
				for(i=0 ; i < mem; i += 1 )
				{
					free(servfich[i]);
				}
				exit(-1);
			}
			else
			{
				if(verbose)
				{
					printf("servidor encontrado para el tld: %s\n",eltld);
				}
				strcpy(servidor,servfich[i]->serv);
				for(i=0 ; i < mem; i += 1 )
				{
					free(servfich[i]);
				}
				if((h=gethostbyname(servidor))!=NULL)
				{
					ip_servidor=(char *)inet_ntoa(*((struct in_addr *)h->h_addr));
				}
				else{
					fprintf(stderr,"servidor %s especificado erroneo para el tld %s\n",servidor,eltld);
					exit(-1);
				}
				return ip_servidor;
			} 		
		}
		else //El servidor por defecto es ARIN
		{
			if(verbose)
			{
				arin=ARIN;
				printf("servidor por defecto ARIN: %s\n",arin);
			}
			return ARIN; 
		}
	}
	else
	{
		if (inet_pton(AF_INET,argumento,&comp.sin_addr)<=0)
		{
			if(verbose)
			{
				printf("buscando servidor a través del alias: tld concatenado con .whois-servers.net\n");
			}
			return resolver_dir_serv(argumento);
		}
		else //El servidor por defecto es ARIN
		{
			if(verbose)
			{
				arin=ARIN;
				printf("servidor por defecto ARIN: %s\n",arin);
			}
			return ARIN; 
		}
	}
}
/* Esta función dada un puerto y una ip, crea un socket y 
conecta dicho socket a través de una conexión TCP 
con el host en la ip especificada en el puerto dado.
*/
int conectar(char * dir_servidor,int puerto)
{
	struct sockaddr_in servidor_addr;
	int sockfd;
	/*Creacion del socket
AF_INET : Protocolos TCP/IP ipv4
SOCK_STREAM: Indica conexión confiable bidireccional 
TCP: protocolo tcp*/
	sockfd = socket(AF_INET, SOCK_STREAM, TCP);
	if (sockfd<0)
	{
		perror("Error al crear el socket");
		exit(-1);
	}
	/*Se pone a cero toda la estructura sockaddr_in de servidor_addr*/
	bzero(&servidor_addr,sizeof(servidor_addr));
	servidor_addr.sin_family = AF_INET;
	/*htons: covierte "puerto" del orden de byte de la máquina 
		donde se este ejecutando el programa a la ordenación 
		de bytes de la red*/
	servidor_addr.sin_port = htons(puerto);
	if (inet_pton(AF_INET,dir_servidor,&servidor_addr.sin_addr)<0)
	{
		perror("inet_pton error");
		exit(-1);
	}
	if(verbose)
	{
		printf("conectando al servidor...\n");
	}
	/*Conecta el socket al servidor*/
	if (connect(sockfd,(struct sockaddr*)&servidor_addr,sizeof(struct sockaddr))<0)
	{
		close(sockfd);
		perror("connect error");
		exit(-1);
	}
	if(verbose)
	{
		printf("conectado!!!\n");
	}
	return sockfd;
}
/*Envia la petición(query) al servidor y recoge la respuesta 
mostrandola por pantalla(en modo DEBUG), devuelve 0 si todo ha ido bien*/
int respuesta_servidor(int sockfd,char *peticion,char buffer[])
{
	int byte_enviados,len,len_recv;
	char pet_final[strlen(peticion)+3];
	strcpy(pet_final,peticion);
	strcat(pet_final,"\x0d\x0a");//CR concatenado con LF
		len=strlen(pet_final);
		/* llamada a send: 
sockfd:socket donde se envian los datos
pet_final: query que se envia al servidor
len: longitud de la query
flag: 0 para este caso */
	if(verbose)
	{
		printf("enviando la consulta al servidor\n");
	}
		byte_enviados=send(sockfd,pet_final,len,0);
		if(byte_enviados!=len)
		{
			close(sockfd);
			fprintf(stderr,"Error: No se ha enviado toda la petición al servidor\n");
			exit(-1); 
		}
		if(verbose)
		{
			printf("enviados %d bytes\n",byte_enviados);
		}
		/* llamada a recv: 
sockfd:socket donde se reciben los datos
buffer: buffer donde se guarda la respuesta
			(size_t)5*1024: longitud del buffer
flag: MSG_WAITALL para que espere todo el mensaje del servidor */
		if(verbose)
		{
			printf("esperando por la respuesta del servidor...\n");
		}
		if ((len_recv=recv(sockfd,(void *)buffer,(size_t)5*1024,MSG_WAITALL)) < 0)
		{
			close(sockfd);
			perror("Error al recibir los mensajes");
			exit(-1);
		}
		if(len_recv==0)
		{
			close(sockfd);
			fprintf(stderr,"La conexión ha sido cortada por el servidor");
			exit(-1);
		}
		buffer[len_recv]='\0';
		if(verbose)
			printf("Bytes recibidos:%d\n",len_recv );
		//printf("%s\n\n",buffer);

		return 0;
}




int leer_servidores_fichero(struct servidor_whois * servfich[])
{
	FILE *df;
	char *fich=FICH;
	unsigned int i=0;
	unsigned int count=0;
	int c;
	struct servidor_whois servidor;
	
	if((df=fopen(FICH,"r"))==NULL)
	{
		fprintf(stderr, "no se puede abrir el fichero %s especificado\n",fich);
		exit(-1);
	}
 	while (count !=MAXSERVFICH)
	{
 		/* Obtiene una servidor del archivo */
		char car;
		while ((car=getc(df))==' ' )
			continue;
		if (car=='\n')
			while ((car=getc(df))==' ' )
				continue;
		while (car=='#')
		{
			while ((car=getc(df))!='\n')
				continue;
			car=getc(df);	
		}
		if(0 !=fseek(df,-1,SEEK_CUR))
		{
			fprintf(stderr, "error en fseek\n");
			exit(-1);
		}
			
		c=fscanf(df,"%s", servidor.tld);		
		if (c==EOF)
			break;
		c=fscanf(df,"%s", servidor.serv);	
		if (c==EOF)
			break;
		servfich[i]=(struct servidor_whois *)malloc((size_t)sizeof(struct servidor_whois));
		count+=1;
	  	strcpy(servfich[i]->tld,servidor.tld);
		strcpy(servfich[i]->serv,servidor.serv);
		i+=1;
	}            
	/* Se repite hasta encontrar EOF */
	fclose(df);
	return count;
}

int principal(char *servidor,struct hostent *h,char *dominio,int puerto,char buffer[])
{
	char *ip_servidor;
	int sockfd;
	if((h=gethostbyname(servidor))!=NULL)
	{
		ip_servidor=(char *)inet_ntoa(*((struct in_addr *)h->h_addr));
	}
	else{
		fprintf(stderr,"servidor %s especificado erroneo\n",servidor);
		exit(-1);
	}
	sockfd=conectar(ip_servidor,puerto);
	respuesta_servidor(sockfd,dominio,buffer);
	close(sockfd);
	return 0;
}

int main(int argc, char** argv)
{
	char buffer[MAX_TAM_BUF];
	//	char *dominio="zumodecebada.com";			// de telefonica?
	char *dominio;		// para almacenar nombre de dominio del que se desea obtener informacion
	char *ip_servidor;
	int sockfd;
	struct hostent *h;
	char *servidor;			// host que se le pasa como argumento para -h
	int flag_h=0;
	char *rir;
	int opt, pal_puerto;
	int flag_f = 0;		// flag para activar la lectura de los servidores whois  
	nombre_prog = argv[0];		// guarda el nom del programa para incluirlo en salida 
	
	if (argc == 1){
		usage();
		exit(EXIT_SUCCESS);
	}
	
	while((opt = getopt(argc, argv, "lvh:p:af")) != -1)	// man 3 getopt
		switch (opt){
			case 'a':
				usage();
				break;
			case 'v':
				verbose = 1;	
				break;
			case 'h':
				flag_h =1;
				servidor = optarg;
				break;
			case 'p':
				whois_port = optarg;  
				break;				
			case 'f':
				flag_f = 1;
				break;
			case 'l':
				info();
				break;			
			default:
				usage();
		}
			argc -=optind;		// se actualiza el numero de argumentos con el indice de argumentos	de getopt
			argv +=optind;		
	
	// en caso  de que no se ingresen argumentos
	
	if (!argc)			// se controla de que exista un nombre de dominio por el que preguntar
		usage();
	
	dominio=*argv;		// se asigna el nombre del dominio pasado por parametro
	argv++;				// es necesario incrementar?
	pal_puerto = atoi(whois_port);		//  tambien se podria poner en todas las siguientes apariciones de pal_puerto atoi(whois_port) y nos ahorramos una variable

	if(!flag_h)	{				// si no se seleciona un servidor especifico (no es necesario ya que deberia
		servidor=resolver_servidor(dominio,flag_f);	
		if (strcmp(servidor,ARIN)==0)//Se le pregunta a ARIN
		{
			principal(servidor,h,dominio,pal_puerto,buffer);
			rir=encontrar_rir(buffer);
			if(rir!=ARIN)
			{
				servidor=rir;
				principal(servidor,h,dominio,pal_puerto,buffer);
			}
			printf("%s\n",buffer);
		}
		else
		{
			ip_servidor=servidor;
			sockfd=conectar(ip_servidor,pal_puerto);
			respuesta_servidor(sockfd,dominio,buffer);
			printf("%s\n",buffer);
			close(sockfd);
		}
	}
	else		// para el servidor especifico introducido como parametro (-h)
	{
		principal(servidor,h,dominio,pal_puerto,buffer);
		printf("%s\n",buffer);		
	}
	return 0;
}

int info(void){
	extern char *__progname;
	printf("\nLicencia de %s GNU General Public License v3\n", __progname); // tambien nombre_prog
	printf("Autores: Eduardo Olalde Aizpurua (eduardo.olaldeATgmail.com)\n"
		   "         Daniel Perdomo Lorenzo (dperdomo.lorenzoATgmail.com)\n"
		   "Version: 0.1\n\n");
	exit(0);
}
//--dead void
int usage(void){
	extern char *__progname;
	fprintf(stdout, "\nUso: %s [-av] [-h host] [-p puerto] nombre\n\n", __progname); // tambien nombre_prog
	printf("    -a  --ayuda              Muestra esta ayuda y sale\n"
		   "    -l  --version            Muestra la version, autores y licencia\n"
		   "    -v  --verbose            Muestra una salida mas informada\n"
		   "    -h  --host               Para especificar la conexion a un servidor concreto\n"
		   "    -p  --puerto             Para especificar un puerto\n\n");
	exit(0);	
}

